package com.pz.chat.netty_ws;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.pz.chat.config.netty.NettyConfig;
import com.pz.chat.utils.SpringUtils;
import io.netty.channel.Channel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

@Component
@Slf4j
@RefreshScope
public class NettyWebSocketServer {
    @Autowired
    private NettyConfig nettyConfig;

    @Autowired
    private NettyServerInitializer nettyServerInitializer;
    @Autowired
    private ThreadPoolExecutor executor;
    @Value("${spring.cloud.nacos.enabled}")
    private int discoveryEnable;

    @PostConstruct
    public void nettyServerStart() {
        // 使用线程 避免netty的阻塞方法阻塞springboot的主线程执行
        CompletableFuture.runAsync(()->{
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(nettyConfig.getBossGroup(), nettyConfig.getWorkerGroup())
                        //设置通道为 NioChannel
                        .channel(NioServerSocketChannel.class)
                        //对出站/入站的事件进行记录
                        .handler(new LoggingHandler(LogLevel.INFO))
                        .childHandler(nettyServerInitializer);
                ChannelFuture future = serverBootstrap.bind(nettyConfig.getWsPort()).sync();
                log.info("netty websocket server started, port={}", nettyConfig.getWsPort());
                if(discoveryEnable==1) {
                    registerNamingService(nettyConfig.getServerName(), nettyConfig.getWsPort());
                    connectOtherServer();
                }
                // 处理 channel 的关闭，sync 方法作用是同步等待 channel 关闭
                // 阻塞
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("netty websocket server exception", e);
                throw new RuntimeException(e);
            } finally {
                log.info("netty websocket server close");
            }
        }, executor);
    }

    /**
     * 将Netty服务注册进 Nacos
     *
     * @param nettyName 服务名称
     * @param nettyPort 服务端口号
     */
    private void registerNamingService(String nettyName, Integer nettyPort) {
        NacosDiscoveryProperties nacosDiscoveryProperties = SpringUtils.getBean("nacosDiscoveryProperties");
        if(nacosDiscoveryProperties != null) {
            try {
                Properties properties = new Properties();
                properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
                properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
                NamingService namingService = NamingFactory.createNamingService(properties);
                String instanceIp =  nettyConfig.getNettyDiscoveryIp() != null ? nettyConfig.getNettyDiscoveryIp() : InetAddress.getLocalHost().getHostAddress();
                namingService.registerInstance(nettyName, instanceIp, nettyPort);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

    /**
     * 服务注册成功后就和其他服务器上的 netty-websocket-server建立连接
     * 方便转发在不同服务器上连接的客户端的消息
     */
    private void connectOtherServer() {
        NacosDiscoveryProperties nacosDiscoveryProperties = SpringUtils.getBean("nacosDiscoveryProperties");
        if(nacosDiscoveryProperties != null) {
            try {
                Properties properties = new Properties();
                properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
                properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
                NamingService namingService = NamingFactory.createNamingService(properties);
                List<Instance> instances = namingService.selectInstances(nettyConfig.getServerName(), true);
                instances.forEach(instance -> CompletableFuture.runAsync(()->{
                    // 获取除开自己以外其他存活的服务器的ip和port
                    if (!instance.getIp().equals(nettyConfig.getNettyDiscoveryIp()) || instance.getPort() != nettyConfig.getWsPort()) {
                        // 与这些服务武器开启ws连接
                        NettyWebSocketClient client = new NettyWebSocketClient();
                        String wsUrl = "ws://"+instance.getIp()+":"+instance.getPort()+nettyConfig.getWsPath();
                        // 将与其他服务的channel存储
                        Channel channel = client.clientStart(wsUrl);
                        nettyConfig.getChannelGroup().add(channel);
                        nettyConfig.getClientMap().put(NettyConfig.serverClientKey(instance.getIp(), instance.getPort()), channel.id());
                    }
                }, executor));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }
}
